package com.gusto.engine.semsim.measures.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.exceptions.PropertyAccessException;
import com.gusto.engine.beans.model.Res;
import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.ResourceSimilarity;
import com.gusto.engine.semsim.measures.SetSimilarity;
import com.gusto.engine.semsim.measures.Similarity;
import com.gusto.engine.semsim.measures.ValueSimilarity;
import com.gusto.engine.semsim.measures.types.SemTypes;

/**
 * <p>The ComposedSimilarity measure allows to combine 
 * a number of weighted similarities on properties of objects.</p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class ComposedSimilarity implements ResourceSimilarity {
	
	private Logger log = Logger.getLogger(ComposedSimilarity.class);
	
	private List<ComposedSimilarityParam> params = new ArrayList<ComposedSimilarityParam>();
	public void setParams(List<ComposedSimilarityParam> params) {
		this.params = params;
	}
	public List<ComposedSimilarityParam> getParams() {
		return params;
	}
	
	public ComposedSimilarity() {
		
	}
	
	public Double getSimilarity(Res res1, Res res2) throws SimilarityException, PropertyAccessException {
		log.debug("Calculating composed similarity");
		
		Double totalSimialrity = 0D;
		Double totalWeight = 0D;
		
		for (ComposedSimilarityParam param : params) {
			
			SemTypes type = param.getType();
			Similarity similarity = param.getSimilarity();
			Double sim = getSimilarity(res1, res2, param.getProperty(), type, similarity);
			log.debug("Component " + similarity.getClass().getSimpleName());
			if (sim.equals(Double.NaN)) {
				sim = 0D;
			}
			totalSimialrity += sim * (param.getWeight() * 1.0);
			totalWeight += (param.getWeight() * 1.0);
		}
		log.info("Composed similarity (" + res1  + ", " + res2 + ") = " + (totalSimialrity/totalWeight));
		return (totalSimialrity/totalWeight);
	}
	
	private Double getSimilarity(Res res1, Res res2, String property, SemTypes type, Similarity similarity) throws SimilarityException, PropertyAccessException {
		if (type.equals(SemTypes.VALUE)) {
			ValueSimilarity valueSimilarity = (ValueSimilarity)similarity;
			
			Object lp1 = res1.getValueProperty(property);
			Object lp2 = res2.getValueProperty(property);
			
			if (lp1 != null && lp2 != null) {
				double sim = valueSimilarity.getSimilarity(lp1, lp2);
				return sim;
			}
		}
		else if (type.equals(SemTypes.RESOURCE)) {
			ResourceSimilarity resourceSimilarity = (ResourceSimilarity)similarity;
			
			Res rp1;
			Res rp2;
			if (property == null || property.equals("")) {
				rp1 = res1;
				rp2 = res2;
			}
			else {
				rp1 = res1.getResourceProperty(property);
				rp2 = res2.getResourceProperty(property);
			}
			
			if (rp1 != null && rp2 != null) {
				double sim = resourceSimilarity.getSimilarity(rp1, rp2);
				return sim;
			}
		}
		else if (type.equals(SemTypes.SET)) {
			SetSimilarity setSimilarity = (SetSimilarity)similarity;
			
			if (setSimilarity.getType().equals(SemTypes.RESOURCE)) {
				List<Res> list1 = res1.getResourceSetProperty(property);
				List<Res> list2 = res2.getResourceSetProperty(property);
				return setSimilarity.getSimilarity(list1, list2);
			}
			else if (setSimilarity.getType().equals(SemTypes.VALUE)) {
				List<Object> list1 = res1.getValueSetProperty(property);
				List<Object> list2 = res2.getValueSetProperty(property);
				return setSimilarity.getSimilarity(list1, list2);
			}
		}
		return Double.NaN;
	}
	
	public String getId() {
		String id = "<"  + getClass().getName() + "|";
		for (ComposedSimilarityParam param : params) {
			id += param.getProperty() + "|";
			id += param.getType() + "|";
			id += param.getSimilarity().getId() + "|";
			id += param.getWeight() + "|";
		}
		id += ">";
		return id;
	}
		
}
